////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2001.
// -------------------------------------------------------------------------
//  File name:   SelectionGroup.cpp
//  Version:     v1.00
//  Created:     10/10/2001 by Timur.
//  Compilers:   Visual C++ 6.0
//  Description: CSelectionGroup implementation.
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "SelectionGroup.h"

#include "BaseObject.h"
#include "EntityObject.h"
#include "HyperGraph/FlowGraphNode.h"
#include "HyperGraph/FlowGraph.h"
#include "HyperGraph/FlowGraphManager.h"
#include "PrefabObject.h"
#include "ViewManager.h"
#include "Brush/SolidBrushObject.h"
#include "BrushObject.h"
#include "EntityObject.h"
#include "SegmentedWorld/SegmentedWorldManager.h"


//////////////////////////////////////////////////////////////////////////
CSelectionGroup::CSelectionGroup()
	: m_ref(1), m_bVertexSnapped(false)
{
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::AddObject( CBaseObject *obj )
{
	if (!IsContainObject(obj))
	{
		m_objects.push_back(obj);
		m_objectsSet.insert(obj);
		m_filtered.clear();
	}
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::RemoveObject( CBaseObject *obj )
{
	for (Objects::iterator it = m_objects.begin(); it != m_objects.end(); ++it)
	{
		if (*it == obj)
		{
			m_objects.erase(it);
			m_objectsSet.erase(obj);
			m_filtered.clear();
			break;
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::RemoveAll()
{
	m_objects.clear();
	m_objectsSet.clear();
	m_filtered.clear();
}
	
bool CSelectionGroup::IsContainObject( CBaseObject *obj ) const 
{
	return (m_objectsSet.find(obj) != m_objectsSet.end());
}

//////////////////////////////////////////////////////////////////////////
bool CSelectionGroup::IsEmpty() const
{
	return m_objects.empty();
}

//////////////////////////////////////////////////////////////////////////
bool CSelectionGroup::SameObjectType()
{
	if (IsEmpty())
		return false;
	CBaseObjectPtr pFirst=(*(m_objects.begin()));
	for (Objects::iterator it = m_objects.begin(); it != m_objects.end(); ++it)
	{
		if ((*it)->GetRuntimeClass()!=pFirst->GetRuntimeClass())
			return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
int CSelectionGroup::GetCount() const
{
	return m_objects.size();
}

//////////////////////////////////////////////////////////////////////////
CBaseObject* CSelectionGroup::GetObject( int index ) const
{
	ASSERT( index >= 0 && index < m_objects.size() );
	return m_objects[index];
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::Copy( const CSelectionGroup &from )
{
	m_name = from.m_name;
	m_objects = from.m_objects;
	m_objectsSet = from.m_objectsSet;
	m_filtered = from.m_filtered;
}

//////////////////////////////////////////////////////////////////////////
Vec3	CSelectionGroup::GetCenter() const
{
	Vec3 c(0,0,0);
	for (int i = 0; i < GetCount(); i++)
	{
		c += GetObject(i)->GetWorldPos();
	}
	if (GetCount() > 0)
		c /= GetCount();
	return c;
}

//////////////////////////////////////////////////////////////////////////
AABB CSelectionGroup::GetBounds() const
{
	AABB b;
	AABB box;
	box.Reset();
	for (int i = 0; i < GetCount(); i++)
	{
		GetObject(i)->GetBoundBox( b );
		box.Add( b.min );
		box.Add( b.max );
	}
	return box;
}

//////////////////////////////////////////////////////////////////////////
int CSelectionGroup::GetFlowGraphCount() const
{
	int count = 0;
	CFlowGraphManager *pMgr = GetIEditor()->GetFlowGraphManager();
	for (int i = 0; i < pMgr->GetFlowGraphCount(); ++i)
	{
		CFlowGraph *pFlowGraph = pMgr->GetFlowGraph(i);
		CEntityObject *pEntity = pFlowGraph->GetEntity();
		if (pEntity)
		{
			CBaseObject* pObj = GetIEditor()->GetObjectManager()->FindObject(pEntity->GetId());
			if (pObj && true == IsContainObject(pObj))
				++ count;
		}
	}
	return count;
}

//////////////////////////////////////////////////////////////////////////
int CSelectionGroup::GetFlowGraphReferenceCount() const
{
	int count = 0;
	CFlowGraphManager *pMgr = GetIEditor()->GetFlowGraphManager();
	for (int i = 0; i < pMgr->GetFlowGraphCount(); ++i)
	{
		CFlowGraph *pFlowGraph = pMgr->GetFlowGraph(i);
		CEntityObject *pOwnerEntity = pFlowGraph->GetEntity();
		IHyperGraphEnumerator *pEnum = pFlowGraph->GetNodesEnumerator();
		if (NULL == pEnum)
			continue;

		for (IHyperNode* pChild = pEnum->GetFirst(); pChild; pChild = pEnum->GetNext())
		{
			if ( ((CHyperNode*)pChild)->IsFlowNode() && ((CHyperNode*)pChild)->GetFlowNodeId()!=InvalidFlowNodeId )
			{
				CFlowNode *node = (CFlowNode*)pChild;
				if (NULL == node)
					continue;

				CEntityObject* pEntity = node->GetEntity();
				if (NULL == pEntity || pOwnerEntity == pEntity)
					continue;

				CBaseObject* pObj = GetIEditor()->GetObjectManager()->FindObject(pEntity->GetId());
				if (NULL == pObj)
					continue;

				if (true == IsContainObject(pObj))
					++count;
			}
		}
	}
	return count;
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::FilterParents()
{
	if (!m_filtered.empty())
		return;

	m_filtered.reserve( m_objects.size() );
	for (int i = 0; i < m_objects.size(); i++)
	{
		CBaseObject *obj = m_objects[i];
		CBaseObject *parent = obj->GetParent();
		bool bParentInSet = false;
		while (parent)
		{
			if (m_objectsSet.find(parent) != m_objectsSet.end())
			{
				bParentInSet = true;
				break;
			}
			parent = parent->GetParent();
		}
		if (!bParentInSet)
		{
			m_filtered.push_back(obj);
		}
	}
}
//---------------------------------------------
bool CSelectionGroup::SWOnSelectionClonePlaceAccept()
{
	FilterParents();

	for (int i = 0; i < GetFilteredCount(); i++)
	{
		CBaseObject *obj = GetFilteredObject(i);
		SW_TEST_OBJ_PLACETO_MCB2(obj->GetWorldPos(), obj->GetLayer(), true);
	}

	for (int i = 0; i < GetFilteredCount(); i++)
	{
		CBaseObject *obj = GetFilteredObject(i);
		SW_ON_OBJ_NEW(obj);
	}
	return true;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::Move( const Vec3 &offset,bool keepHeight,int referenceCoordSys )
{
	// [MichaelS - 17/3/2005] Removed this code from the three edit functions (move,
	// rotate and scale). This was causing a bug where the render node of objects
	// was not being updated when objects were dragged away from their position
	// and then back again, since movement is re-calculated from the initial position
	// each mouse message (ie first the previous movement is undone and then the
	// movement is applied). This meant that when moving back to the start position
	// it appeared like no movement was applied, although it was still necessary to
	// update the graphics resources. The object transform is explicitly reset
	// below.

	//if (offset.x == 0 && offset.y == 0 && offset.z == 0)
	//	return;

	m_bVertexSnapped = false;

	FilterParents();

	Vec3 newPos;
	for (int i = 0; i < GetFilteredCount(); i++)
	{
		CBaseObject *obj = GetFilteredObject(i);
		
		Matrix34 wtm = obj->GetWorldTM();
		Vec3 wp = wtm.GetTranslation();

		bool bSnapToVertex = gSettings.vertexSnappingSettings.bOn
											&& GetFilteredCount() == 1
											&& (obj->GetType() == OBJTYPE_BRUSH || obj->GetType() == OBJTYPE_SOLID 
													|| obj->IsKindOf(RUNTIME_CLASS(CGeomEntity)));
		if (bSnapToVertex)
			newPos = wp + SnapToCloseVertexIfAny(obj, offset);
		else
			newPos = wp + offset;
		if (keepHeight)
		{
			// Make sure object keeps it height.
			float height = wp.z - GetIEditor()->GetTerrainElevation( wp.x,wp.y );
			newPos.z = GetIEditor()->GetTerrainElevation( newPos.x,newPos.y ) + height;
		}
		
		obj->SetWorldPos(newPos, TM_USER_INPUT | TM_POS_CHANGED | TM_BY_MOVE_TOOL);
	}
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::MoveTo( const Vec3& pos, bool keepHeight, int referenceCoordSys )
{
	FilterParents();
	if( GetFilteredCount() < 1 )
		return;

	CBaseObject * refObj = GetFilteredObject(0);
	CSelectionGroup::Move(pos - refObj->GetWorldTM().GetTranslation(), keepHeight, referenceCoordSys);
}


//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::Rotate(const Quat &qRot, int referenceCoordSys)
{
  Matrix34 rotateTM;
  rotateTM.SetIdentity();
  rotateTM = Matrix33(qRot) * rotateTM;
  
  Rotate(rotateTM, referenceCoordSys);
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::Rotate( const Ang3& angles,int referenceCoordSys )
{
	//if (angles.x == 0 && angles.y == 0 && angles.z == 0)
	//	return;

	// Rotate selection about selection center.
	Vec3 center = GetCenter();

	Matrix34 rotateTM;
	rotateTM.SetIdentity();
	//rotateTM.RotateMatrix_fix( angles );
	rotateTM = rotateTM*Matrix34::CreateRotationXYZ(DEG2RAD(-angles)); //NOTE: angles in radians and negated 

  Rotate(rotateTM, referenceCoordSys);
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::Rotate(const Matrix34 &rotateTM, int referenceCoordSys)
{
  // Rotate selection about selection center.
  Vec3 center = GetCenter();
	Matrix34 ToOrigin;
	Matrix34 FromOrigin;

	ToOrigin.SetIdentity();
	FromOrigin.SetIdentity();

	if (referenceCoordSys != COORDS_LOCAL)
	{
		ToOrigin.SetTranslation( -center );
		FromOrigin.SetTranslation( center );

		if (referenceCoordSys == COORDS_USERDEFINED)
		{
			Matrix34 userTM = GetIEditor()->GetViewManager()->GetGrid()->GetMatrix();
			Matrix34 invUserTM = userTM.GetInvertedFast();

			ToOrigin = invUserTM*ToOrigin;
			FromOrigin = FromOrigin*userTM;
		}
	}

	FilterParents();

	for (int i = 0; i < GetFilteredCount(); i++)
	{
		CBaseObject *obj = GetFilteredObject(i);
		
		Matrix34 m = obj->GetWorldTM();
		if (referenceCoordSys != COORDS_LOCAL)
		{
			bool bBoneAttached = false;
			if (referenceCoordSys == COORDS_PARENT && obj->IsKindOf(RUNTIME_CLASS(CEntityObject)))
			{
				// Here, a special care is needed for the parent coordinate rotation of the bone attached entity.
				CEntityObject *pEntity = static_cast<CEntityObject*>(obj);
				QuatT parentXform, relXform;
				if (pEntity->GetBoneAttachTransforms(parentXform, relXform))
				{
					Matrix34 parentTM =  Matrix34(parentXform);
					parentTM.OrthonormalizeFast();
					parentTM.SetTranslation(Vec3(0, 0, 0));
					Matrix34 invParentTM = parentTM.GetInvertedFast();

					m = FromOrigin * parentTM * rotateTM * invParentTM * ToOrigin * m;
					bBoneAttached = true;
				}
			}

			if (!bBoneAttached)
			{
				if (referenceCoordSys == COORDS_PARENT && obj->GetParent())
				{
					Matrix34 parentTM = obj->GetParent()->GetWorldTM();
					parentTM.OrthonormalizeFast();
					parentTM.SetTranslation(Vec3(0, 0, 0));
					Matrix34 invParentTM = parentTM.GetInvertedFast();

					m = FromOrigin * parentTM * rotateTM * invParentTM * ToOrigin * m;
				}
				else
				{			
					m = FromOrigin * rotateTM * ToOrigin * m;
				}
			}
		}
		else
		{
			m = m * rotateTM;
		}
		obj->SetWorldTM( m,TM_USER_INPUT );
		obj->InvalidateTM( TM_USER_INPUT );
	}
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::Scale( const Vec3 &scale,int referenceCoordSys )
{
	//if (scale.x == 1 && scale.y == 1 && scale.z == 1)
	//	return;

	Vec3 scl = scale;
	if (scl.x == 0) scl.x = 0.01f;
	if (scl.y == 0) scl.y = 0.01f;
	if (scl.z == 0) scl.z = 0.01f;

	// Scale selection relative to selection center.
	Vec3 center = GetCenter();

	Matrix34 scaleTM;
	scaleTM.SetIdentity();
	scaleTM=Matrix33::CreateScale( Vec3(scl.x,scl.y,scl.z) ) * scaleTM;

	Matrix34 ToOrigin;
	Matrix34 FromOrigin;

	ToOrigin.SetIdentity();
	FromOrigin.SetIdentity();

	if (referenceCoordSys != COORDS_LOCAL)
	{
		ToOrigin.SetTranslation( -center );
		FromOrigin.SetTranslation( center );
	}

	FilterParents();

	for (int i = 0; i < GetFilteredCount(); i++)
	{
		CBaseObject *obj = GetFilteredObject(i);	
		Matrix34 m = obj->GetWorldTM();

		//Vec3 vResetScale(1,1,1);
		//vResetScale.Set( 1/obj->GetScale().x, 1/obj->GetScale().y, 1/obj->GetScale().z );

		if (referenceCoordSys != COORDS_LOCAL)
		{
			//Matrix34 resetScaleTM;
			//resetScaleTM.SetIdentity();
			//resetScaleTM=Matrix33::CreateScale( Vec3(vResetScale.x,vResetScale.y,vResetScale.z) ) * resetScaleTM;

			//// Reset scale back to 1
			//m = FromOrigin * resetScaleTM * ToOrigin * m;

			// Apply new scale
			m = FromOrigin * scaleTM * ToOrigin * m;
		}
		else
		{
			//// Reset scale back to 1
			//m.Scale(vResetScale);

			// Apply new scale
			m = m * scaleTM;
		}

		obj->SetWorldTM( m,TM_USER_INPUT | TM_BY_SCALE_TOOL | ENTITY_XFORM_SCL );
		obj->InvalidateTM( TM_USER_INPUT | TM_BY_SCALE_TOOL | ENTITY_XFORM_SCL );
	}
}


void CSelectionGroup::StartScaling()
{
	for (int i = 0; i < GetFilteredCount(); i++)
	{
		CBaseObject *obj = GetFilteredObject(i);
		obj->StartScaling();
	}
}


void CSelectionGroup::FinishScaling( const Vec3 &scale, int referenceCoordSys )
{
	if( fabs(scale.x-scale.y) < 0.001f && 
			fabs(scale.y-scale.z) < 0.001f && 
			fabs(scale.z-scale.x) < 0.001f )
	{
		return;
	}

	for (int i = 0; i < GetFilteredCount(); ++i)
	{
		CBaseObject *obj = GetFilteredObject(i);
		Vec3 OriginalScale;
		if( obj->GetUntransformedScale(OriginalScale) )
		{
			obj->TransformScale(scale);
			obj->SetScale(OriginalScale);
		}
	}
}


//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::Align()
{
	for (int i = 0; i < GetFilteredCount(); ++i)
	{
		bool terrain = false;
		CBaseObject *obj = GetFilteredObject(i);
		Vec3 pos = obj->GetPos();
		Quat rot = obj->GetRotation();
		CPoint point = GetIEditor()->GetActiveView()->WorldToView(pos);
		Vec3 normal = GetIEditor()->GetActiveView()->ViewToWorldNormal(point, false, true);
		pos =	GetIEditor()->GetActiveView()->ViewToWorld( point, &terrain, false, false, true);
		Vec3 zaxis = rot * Vec3(0,0,1);
		normal.Normalize();
		zaxis.Normalize();
		Quat nq;
		nq.SetRotationV0V1(zaxis, normal);
		obj->SetRotation(nq * rot);
		obj->SetPos(pos);
	}
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::Transform(const Vec3 &offset, bool keepHeight, const Ang3 &angles, const Vec3 &scale, int referenceCoordSys)
{
  if (offset != Vec3(0))
    Move(offset, keepHeight, referenceCoordSys);

  if (!(angles == Ang3(ZERO)))
    Rotate(angles, referenceCoordSys);

  if (scale != Vec3(0))
    Scale(scale, referenceCoordSys);
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::ResetTransformation()
{
	FilterParents();
  Quat qIdentity;
  qIdentity.SetIdentity();
  Vec3 vScale(1.0f, 1.0f, 1.0f);

  for (int i = 0, n = GetFilteredCount(); i < n; ++ i) {
    CBaseObject *pObj = GetFilteredObject(i);
    pObj->SetRotation(qIdentity);
    pObj->SetScale(vScale);
  }
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::Clone( CSelectionGroup &newGroup )
{
	IObjectManager *pObjMan = GetIEditor()->GetObjectManager();
	assert( pObjMan );

	int i;
	CObjectCloneContext cloneContext;

	SW_IGNORE_CHANGES_AUTOGUARD;

	FilterParents();

	//////////////////////////////////////////////////////////////////////////
	// Clone every object.
	for (i = 0; i < GetFilteredCount(); i++)
	{
		CBaseObject *pFromObject = GetFilteredObject(i);
		CBaseObject *newObj = pObjMan->CloneObject( pFromObject );
		assert( newObj );

		cloneContext.AddClone( pFromObject,newObj );
		newGroup.AddObject( newObj );
	}

	//////////////////////////////////////////////////////////////////////////
	// Only after everything was cloned, call PostClone on all cloned objects.
	for (i = 0; i < GetFilteredCount(); i++)
	{
		CBaseObject *pFromObject = GetFilteredObject(i);
		CBaseObject *pClonedObject = newGroup.GetObject(i);
		if(pClonedObject)
			pClonedObject->PostClone( pFromObject,cloneContext );
	}

}

//////////////////////////////////////////////////////////////////////////
static void RecursiveFlattenHierarchy( CBaseObject *pObj,CSelectionGroup &newGroup )
{
	newGroup.AddObject( pObj );

	if(!pObj->IsKindOf(RUNTIME_CLASS(CPrefabObject)))
	{
		for (int i = 0; i < pObj->GetChildCount(); i++)
		{
			RecursiveFlattenHierarchy( pObj->GetChild(i),newGroup );
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::FlattenHierarchy( CSelectionGroup &newGroup )
{
	for (int i = 0; i < GetCount(); i++)
	{
		RecursiveFlattenHierarchy( GetObject(i),newGroup );
	}
}

//////////////////////////////////////////////////////////////////////////
class CAttachToParentPickCallback : public IPickObjectCallback
{
public:
	CAttachToParentPickCallback() { m_bActive = true; };
	//! Called when object picked.
	virtual void OnPick( CBaseObject *picked )
	{
		CUndo undo( "Attach Selection" );

		CSelectionGroup *selGroup = GetIEditor()->GetSelection();
		selGroup->FilterParents();

#ifdef SEG_WORLD
		if (!CSWManager::Get().GetDoc().CanModify(picked, false, false))
		{
			OnCancelPick();
			return;
		}

		for (int i = 0; i < selGroup->GetFilteredCount(); i++)
		{
			if (ChildIsValid( picked,selGroup->GetFilteredObject(i) ))
			{
				if (!CSWManager::Get().GetDoc().CanModify(selGroup->GetFilteredObject(i), true, false))
				{
					OnCancelPick();
					return;
				}
			}
		}
#endif

		//bool bSWWasIgnoringChanges = CSWManager::IsIgnoringChanges();
		for (int i = 0; i < selGroup->GetFilteredCount(); i++)
		{
			if (ChildIsValid( picked,selGroup->GetFilteredObject(i) ))
			{
				SW_ON_OBJ_DEL(selGroup->GetFilteredObject(i));

				{
					SW_IGNORE_CHANGES_AUTOGUARD;
					picked->AttachChild( selGroup->GetFilteredObject(i) );
				}
			}
		}

		SW_ON_OBJ_MOD(picked);
		
		m_bActive = false;
		delete this;
	}
	//! Called when pick mode cancelled.
	virtual void OnCancelPick()
	{
		m_bActive = false;
		delete this;
	}
	//! Return true if specified object is pickable.
	virtual bool OnPickFilter( CBaseObject *filterObject )
	{
		return true;
	}

	//////////////////////////////////////////////////////////////////////////
	bool ChildIsValid(CBaseObject *pParent, CBaseObject *pChild, int nDir=3)
	{
		if (!pParent)
			return false;
		if (!pChild)
			return false;
		if (pParent==pChild)
			return false;
		CBaseObject *pObj;
		if (nDir & 1)
		{
			if (pObj=pChild->GetParent())
			{
				if (!ChildIsValid(pParent, pObj, 1))
				{
					return false;
				}
			}
		}
		if (nDir & 2)
		{
			for (int i=0;i<pChild->GetChildCount();i++)
			{
				if (pObj=pChild->GetChild(i))
				{
					if (!ChildIsValid(pParent, pObj, 2))
					{
						return false;
					}
				}
			}
		}
		return true;
	}

	static bool IsActive() { return m_bActive; }
private:
	static bool m_bActive;
};
bool CAttachToParentPickCallback::m_bActive = false;

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::PickAndAttach()
{
	CAttachToParentPickCallback *pCallback = new CAttachToParentPickCallback;
	GetIEditor()->PickObject( pCallback,0,"Attach Selection To Parent" );
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::SendEvent( ObjectEvent event )
{
	for (int i = 0; i < m_objects.size(); i++)
	{
		CBaseObject *obj = m_objects[i];
		obj->OnEvent( event );
	}
}
//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::BeginEditParams( IEditor *ie,int flags )
{
	// For now, nothing to do.
}
//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::EndEditParams()
{
	IEditor*	piEditor(GetIEditor());

	size_t	nObjectCount(m_objects.size());
	size_t	nCurrentObject(0);

	for (nCurrentObject=0;nCurrentObject<nObjectCount;++nCurrentObject)
	{
		m_objects[nCurrentObject]->EndEditParams(piEditor);
	}
}
//////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////
ULONG STDMETHODCALLTYPE CSelectionGroup::AddRef()
{
	return ++m_ref;
};
//////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////
ULONG STDMETHODCALLTYPE CSelectionGroup::Release() 
{ 
	if( (--m_ref) == 0 )
	{
		delete this;
		return 0; 
	}
	else
		return m_ref;
}
//////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////
Vec3 CSelectionGroup::SnapToCloseVertexIfAny(CBaseObject *pObj, const Vec3& offset)
{
	assert(pObj->GetType() == OBJTYPE_SOLID || pObj->GetType() == OBJTYPE_BRUSH
				|| pObj->IsKindOf(RUNTIME_CLASS(CGeomEntity)));

	CSolidBrushObject *pSolidBrushObj = NULL;
	CBrushObject *pBrushObj = NULL;
	CGeomEntity *pGeomEntity = NULL;
	if(pObj->GetType() == OBJTYPE_SOLID)
		pSolidBrushObj = static_cast<CSolidBrushObject*>(pObj);
	else if(pObj->GetType() == OBJTYPE_BRUSH)
		pBrushObj = static_cast<CBrushObject*>(pObj);
	else if(pObj->IsKindOf(RUNTIME_CLASS(CGeomEntity)))
		pGeomEntity = static_cast<CGeomEntity*>(pObj);
	else
		return offset;

	///1. Get the candidate objects.
	std::vector<CBrushObject*> brushes;
	std::vector<CSolidBrushObject*> solids;
	std::vector<CGeomEntity*> entities;
	CollectCandidates(brushes, solids, entities, pObj, offset);
	if(brushes.empty() && solids.empty() && entities.empty())
		return offset;

	///2. Get the vertices of the source object.
	std::vector<Vec3> srcVertices;
	if(pSolidBrushObj)
		pSolidBrushObj->GetVerticesInWorld(srcVertices);
	else if(pBrushObj)
		pBrushObj->GetVerticesInWorld(srcVertices);
	else
		pGeomEntity->GetVerticesInWorld(srcVertices);
	
	if( srcVertices.size() > SnappingVertexNumThreshold  )
		return offset;

	///3. Find the nearest vertex to snap to.
	Vec3 newOffset = FindNearestSnapVertex(srcVertices, offset, brushes, solids, entities);

	return newOffset;
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::IndicateSnappingVertex( DisplayContext &dc ) const
{
	if(m_bVertexSnapped == false)
		return;

	dc.DepthTestOff();

	ColorB pointColor(0,255,0,255);

	dc.pRenderAuxGeom->DrawPoint(m_snapVertex, pointColor, 7);

	dc.DepthTestOn();
}


//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::CollectCandidates(std::vector<CBrushObject*>& brushes, 
																				std::vector<CSolidBrushObject*>& solids, 
																				std::vector<CGeomEntity*>& entities,
																				CBaseObject * pObj, const Vec3& offset)
{
	AABB aabb;
	pObj->GetBoundBox(aabb);
	aabb.Move(offset);
	float range = gSettings.vertexSnappingSettings.snapRange;
	aabb.Expand(Vec3(range,range,range));
	std::vector<CBaseObject*> objects;
	GetIEditor()->GetObjectManager()->FindObjectsInAABB(aabb, objects);
	for(size_t i=0; i<objects.size(); ++i)
	{
		if(objects[i] == pObj)
			continue;

		if(objects[i]->GetType() == OBJTYPE_BRUSH)
			brushes.push_back(static_cast<CBrushObject*>(objects[i]));
		else if(objects[i]->GetType() == OBJTYPE_SOLID)
			solids.push_back(static_cast<CSolidBrushObject*>(objects[i]));
		else if(objects[i]->IsKindOf(RUNTIME_CLASS(CGeomEntity)))
			entities.push_back(static_cast<CGeomEntity*>(objects[i]));
	}
}

Vec3 CSelectionGroup::FindNearestSnapVertex(std::vector<Vec3> &srcVertices, const Vec3& offset, 
																						const std::vector<CBrushObject*>& brushes, 
																						const std::vector<CSolidBrushObject*>& solids,
																						const std::vector<CGeomEntity*>& entities)
{
	bool found = false;;
	Vec3 offsetFound, dstPos;
	float range = gSettings.vertexSnappingSettings.snapRange;
	float distanceFound = range * range;
	for(size_t i=0; i<srcVertices.size(); ++i)
	{
		srcVertices[i] += offset;

		// for candidate brushes
		for(size_t k=0; k<brushes.size(); ++k)
		{
			std::vector<Vec3> dstVertices;
			brushes[k]->GetVerticesInWorld(dstVertices);

			if( dstVertices.size() > SnappingVertexNumThreshold  )
				continue;

			for(size_t m=0; m<dstVertices.size(); ++m)
			{
				Vec3 dv = dstVertices[m] - srcVertices[i];
				float d = dv.GetLengthSquared();
				if(d <= distanceFound)
				{
					found = true;
					distanceFound = d;
					offsetFound = dv;
					dstPos = dstVertices[m];
				}
			}
		}

		// for candidate solids
		for(size_t k=0; k<solids.size(); ++k)
		{
			std::vector<Vec3> dstVertices;
			solids[k]->GetVerticesInWorld(dstVertices);

			if( dstVertices.size() > SnappingVertexNumThreshold  )
				continue;

			for(size_t m=0; m<dstVertices.size(); ++m)
			{
				Vec3 dv = dstVertices[m] - srcVertices[i];
				float d = dv.GetLengthSquared();
				if(d <= distanceFound)
				{
					found = true;
					distanceFound = d;
					offsetFound = dv;
					dstPos = dstVertices[m];
				}
			}
		}

		// for candidate entities
		for(size_t k=0; k<entities.size(); ++k)
		{
			std::vector<Vec3> dstVertices;
			entities[k]->GetVerticesInWorld(dstVertices);

			if( dstVertices.size() > SnappingVertexNumThreshold  )
				continue;

			for(size_t m=0; m<dstVertices.size(); ++m)
			{
				Vec3 dv = dstVertices[m] - srcVertices[i];
				float d = dv.GetLengthSquared();
				if(d <= distanceFound)
				{
					found = true;
					distanceFound = d;
					offsetFound = dv;
					dstPos = dstVertices[m];
				}
			}
		}
	}	
	
	if(found)
	{
		m_bVertexSnapped = true;
		m_snapVertex = dstPos;
		return offset + offsetFound;
	}
	else
		return offset;
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::ObjectModified() const
{
	//Object has been moved/scaled/rotated, call reset
	//some objects script code may rely on the pos/dir of the object and need to be reinitialized
	
	for (int i = 0; i < m_objects.size(); i++)
	{
		CBaseObject *obj = m_objects[i];
		if(obj->GetType() == OBJTYPE_ENTITY)
		{
			CEntityObject* pEnt = (CEntityObject*)obj;
			if(pEnt)
			{
				IEntity* pIEnt = pEnt->GetIEntity();
				if(pIEnt)
				{
					IScriptTable *pTable = pIEnt->GetScriptTable();
					if(pTable)
					{
						Script::CallMethod( pTable,"OnPropertyChange" );
					}
				}
			}
		}
	}
}
